package uk.co.leandro.views
{
	import fl.transitions.Tween;
	import fl.transitions.TweenEvent;
	import fl.transitions.easing.*;
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import uk.co.leandro.events.GemEvent;
	import uk.co.leandro.events.ScoreEvent;
	
	public class Board extends Sprite
	{
		private static const NUM_ROWS_COLS:Number = 8;
		private static const GEM_OFFSET:Number = 1;
		private static const CEL_SIZE:Number = 40;
		private static const TOP:int = 0;
		private static const BOTTOM:int = 1;
		private static const LEFT:int = 2;
		private static const RIGHT:int = 3;
		private static const VERTICALCENTRE:int = 4;
		private static const HORIZONTALCENTRE:int = 5;
		
		private var allGems:Vector.<Vector.<Gem>>;
		private var selectedGems:Vector.<Gem>;
		private var gemsToCollapse:Object = {};
		
		public function Board()
		{
			var vRow:Vector.<Gem>;
			allGems = new Vector.<Vector.<Gem>>();
			allGems.length = NUM_ROWS_COLS;
			allGems.fixed = true;
			
			selectedGems = new Vector.<Gem>();
			
			for (var row:int = 0; row < NUM_ROWS_COLS; row++)
			{
				vRow = new Vector.<Gem>();
				vRow.length = NUM_ROWS_COLS;
				vRow.fixed = true;
				allGems[row] = vRow;
				
				for (var col:int = 0; col < NUM_ROWS_COLS; col++)
				{
					var gem:Gem =  new Gem(row, col);
					vRow[col] = gem;
					
					gem.x = GEM_OFFSET * col + col * CEL_SIZE;
					gem.y = GEM_OFFSET * row + row * CEL_SIZE;
					
					addChild (gem);
					gem.addEventListener(GemEvent.CLICKED, onGemClicked);
				}
			}
			makeNoMatch();
		}
		
		private function makeNoMatch():void
		{
			//checks from bottom right to top left
			var gem:Gem;
			for (var row:int = NUM_ROWS_COLS -1 ; row >= 0; row--)
			{
				for (var col:int = NUM_ROWS_COLS - 1; col >= 0; col--)
				{
					gem = allGems[row][col];
					var thirdGem:Gem;
					//check row
					if (match3(gem, LEFT))
					{
						thirdGem = allGems[row][col-2];
						while((matchesAdjacent(thirdGem, RIGHT) != null))
						{
							thirdGem.randomise();
						}
					}
					//check column
					if (match3(gem, TOP))
					{
						thirdGem = allGems[row-2][col];
						while(matchesAdjacent(thirdGem, BOTTOM) != null)
						{
							thirdGem.randomise();
						}
					}
				}
			}
		}
		
		private function match3(gem:Gem, direction:int):Boolean
		{
			switch(direction)
			{
				case TOP:
				{
					return ((matchesAdjacent(gem, TOP) != null) && (matchesAdjacent(allGems[gem.row-1][gem.column], TOP) != null));
				}
				case BOTTOM:
				{
					return ((matchesAdjacent(gem, BOTTOM) != null) && (matchesAdjacent(allGems[gem.row+1][gem.column], BOTTOM) != null));
				}
				case LEFT:
				{
					return ((matchesAdjacent(gem, LEFT) != null) && (matchesAdjacent(allGems[gem.row][gem.column-1], LEFT) != null));
				}
				case RIGHT:
				{
					return ((matchesAdjacent(gem, RIGHT) != null) && (matchesAdjacent(allGems[gem.row][gem.column+1], RIGHT) != null));
				}
				case VERTICALCENTRE:
				{
					return ((matchesAdjacent(gem, TOP) != null) && (matchesAdjacent(gem, BOTTOM) != null));
				}
				case HORIZONTALCENTRE:
				{
					return ((matchesAdjacent(gem, LEFT) != null) && (matchesAdjacent(gem, RIGHT) != null));
				}
			}
			return false;
		}
		
		private function matchesAdjacent(gem:Gem, direction:int):Gem
		{
			switch(direction)
			{
				case TOP:
				{
					if(gem.row == 0) return null;
					if (gem.colourIndex == allGems[gem.row-1][gem.column].colourIndex) return allGems[gem.row-1][gem.column];
					break;
				}
				case BOTTOM:
				{
					if(gem.row == NUM_ROWS_COLS-1) return null;
					if (gem.colourIndex == allGems[gem.row+1][gem.column].colourIndex) return allGems[gem.row+1][gem.column];
					break;
				}
				case LEFT:
				{
					if(gem.column == 0) return null;
					if (gem.colourIndex == allGems[gem.row][gem.column-1].colourIndex) return allGems[gem.row][gem.column-1];
					break;
				}
				case RIGHT:
				{
					if(gem.column == NUM_ROWS_COLS-1) return null;
					if (gem.colourIndex == allGems[gem.row][gem.column+1].colourIndex) return allGems[gem.row][gem.column+1];
					break;
				}
			}
			return null;
		}
		
		private function checkForMatches(gem:Gem):Array
		{
			var queue:Array = [];
			queue.push(gem);
			var nodeIndex:int = 0;
			var gem2:Gem;
			var gem3:Gem;
			while(queue.length > nodeIndex)
			{
				gem = queue[nodeIndex];
				//match3 top
				if (match3(gem, TOP))
				{
					if (!gem.destroy) gem.destroy = true;
					gem2 = allGems[gem.row-1][gem.column];
					if (!gem2.destroy)
					{
						queue.push(gem2);
						gem2.destroy = true;
					}
					gem3 = allGems[gem.row-2][gem.column];
					if (!gem3.destroy)
					{
						queue.push(gem3);
						gem3.destroy = true;
					}
				}
				//match3 bottom
				if (match3(gem, BOTTOM))
				{
					if (!gem.destroy) gem.destroy = true;
					gem2 = allGems[gem.row+1][gem.column];
					if (!gem2.destroy)
					{
						queue.push(gem2);
						gem2.destroy = true;
					}
					gem3 = allGems[gem.row+2][gem.column];
					if (!gem3.destroy)
					{
						queue.push(gem3);
						gem3.destroy = true;
					}
				}
				//match3 left
				if (match3(gem, LEFT))
				{
					if (!gem.destroy) gem.destroy = true;
					gem2 = allGems[gem.row][gem.column-1];
					if (!gem2.destroy)
					{
						queue.push(gem2);
						gem2.destroy = true;
					}
					gem3 = allGems[gem.row][gem.column-2];
					if (!gem3.destroy)
					{
						queue.push(gem3);
						gem3.destroy = true;
					}
				}
				//match3 right
				if (match3(gem, RIGHT))
				{
					if (!gem.destroy) gem.destroy = true;
					gem2 = allGems[gem.row][gem.column+1];
					if (!gem2.destroy)
					{
						queue.push(gem2);
						gem2.destroy = true;
					}
					gem3 = allGems[gem.row][gem.column+2];
					if (!gem3.destroy)
					{
						queue.push(gem3);
						gem3.destroy = true;
					}
				}
				//match3 vertically with gem in the middle
				if (match3(gem, VERTICALCENTRE))
				{
					if (!gem.destroy) gem.destroy = true;
					gem2 = allGems[gem.row-1][gem.column];
					if (!gem2.destroy)
					{
						queue.push(gem2);
						gem2.destroy = true;
					}
					gem3 = allGems[gem.row+1][gem.column];
					if (!gem3.destroy)
					{
						queue.push(gem3);
						gem3.destroy = true;
					}
				}
				//match3 horizontally with gem in the middle
				if (match3(gem, HORIZONTALCENTRE))
				{
					if (!gem.destroy) gem.destroy = true;
					gem2 = allGems[gem.row][gem.column+1];
					if (!gem2.destroy)
					{
						queue.push(gem2);
						gem2.destroy = true;
					}
					gem3 = allGems[gem.row][gem.column-1];
					if (!gem3.destroy)
					{
						queue.push(gem3);
						gem3.destroy = true;
					}
				}
				
				nodeIndex++;
			}
			return (queue.length >= 3) ? queue : [];
		}
		
		private function onGemClicked(event:GemEvent):void
		{
			var gem:Gem = event.gem;
			switch(selectedGems.length)
			{
				case 0:
				{
					gem.select();
					selectedGems.push(gem);
					break;
				}
				case 1:
				{
					var gem2:Gem = selectedGems[0];
					if(areAdjacent(gem, gem2))
					{
						selectedGems.push(gem);
						gem2.deselect();
						SwapGems();
					}
					else
					{
						gem2.deselect();
						selectedGems.splice(0,1);
					}
					break;
				}
			}
		}
		
		private function areAdjacent(gem1:Gem, gem2:Gem):Boolean
		{
			return ( ( (gem1.row == gem2.row) && (Math.abs(gem1.column-gem2.column) == 1) ) ||
					 ( (gem1.column == gem2.column) && (Math.abs(gem1.row-gem2.row) == 1) ) );
		}
		
		private function onGemDeselected(event:GemEvent):void
		{
			var index:int = selectedGems.indexOf(event.gem);
			selectedGems.splice(index, 1);
		}
		
		private function SwapGems():void
		{
			var gem1:Gem = selectedGems[0];
			var gem2:Gem = selectedGems[1];
			var tween:Tween;
			if (gem1.row == gem2.row)
			{
				tween = swapHorizontal();
			}
			else
			{
				tween = swapVertical();
			}
			allGems[gem1.row][gem1.column] = gem2;
			allGems[gem2.row][gem2.column] = gem1;
			var gem1Row:int = gem1.row;
			var gem1Col:int = gem1.column;
			gem1.row = gem2.row;
			gem1.column = gem2.column;
			gem2.row = gem1Row;
			gem2.column = gem1Col;
			tween.addEventListener(TweenEvent.MOTION_FINISH, gemsFinishedSwappingHandler);
		}
		
		private function swapHorizontal():Tween
		{
			var tween1:Tween = new Tween(selectedGems[0], "x", Strong.easeInOut, selectedGems[0].x, selectedGems[1].x, 0.3, true);
			var tween2:Tween = new Tween(selectedGems[1], "x", Strong.easeInOut, selectedGems[1].x, selectedGems[0].x, 0.3, true);
			return tween1;
		}
		
		private function swapVertical():Tween
		{
			var tween1:Tween = new Tween(selectedGems[0], "y", Strong.easeInOut, selectedGems[0].y, selectedGems[1].y, 0.3, true);
			var tween2:Tween = new Tween(selectedGems[1], "y", Strong.easeInOut, selectedGems[1].y, selectedGems[0].y, 0.3, true);
			return tween1;
		}
		
		private function gemsFinishedSwappingHandler(event:TweenEvent):void
		{
			Tween(event.currentTarget).removeEventListener(TweenEvent.MOTION_FINISH, gemsFinishedSwappingHandler);
			
			var gem1:Gem = selectedGems[0];
			var gem2:Gem = selectedGems[1];
			if (gem1.colourIndex == gem2.colourIndex) revertSwap();
			else
			{
				var gem1Matches:Array = checkForMatches(gem1);
				var gem2Matches:Array = checkForMatches(gem2);
				if ((gem1Matches.length>=3) || (gem2Matches.length>=3))
				{
					destroyGems(gem1Matches.concat(gem2Matches));
				}
				else
				{
					revertSwap();
				}
			}
		}
		
		private function revertSwap():void
		{
			var gem1:Gem = selectedGems[0];
			var gem2:Gem = selectedGems[1];
			
			allGems[gem1.row][gem1.column] = gem2;
			allGems[gem2.row][gem2.column] = gem1;
			var gem1Row:int = gem1.row;
			var gem1Col:int = gem1.column;
			gem1.row = gem2.row;
			gem1.column = gem2.column;
			gem2.row = gem1Row;
			gem2.column = gem1Col;
			
			if (gem1.row == gem2.row)
			{
				swapHorizontal();
			}
			else
			{
				swapVertical();
			}
			selectedGems = new Vector.<Gem>();
		}
		
		private function destroyGems(queue:Array):void
		{
			var gemAbove:Gem;
			var row:int = NUM_ROWS_COLS;
			var newGems:Array = [];
			var gemId:String = "";
			for each (var gem:Gem in queue)
			{
				removeChild(gem);
				gem.removeEventListener(GemEvent.CLICKED, onGemClicked);
				row = gem.row;
				while (row > 0)
				{
					gemAbove = allGems[row-1][gem.column];
					allGems[row][gem.column] = gemAbove;
					gemAbove.row = row;
					gemId = gemAbove.row.toString() + gemAbove.column.toString();
					if (!gemAbove.destroy) gemsToCollapse[gemId] = gemAbove;
					row--;
				}
				
				var newGem:Gem = new Gem(0, gem.column);
				newGem.addEventListener(GemEvent.CLICKED, onGemClicked);
				var numGemsSameColumn:int = 1;
				for each (var nGem:Gem in newGems) 
				{
					if (gem.column == nGem.column) numGemsSameColumn++;
				}
				newGems.push(newGem);
				
				allGems[0][gem.column] = newGem;
				newGem.x = GEM_OFFSET * newGem.column + newGem.column * CEL_SIZE;
				newGem.y = (GEM_OFFSET * numGemsSameColumn + numGemsSameColumn * CEL_SIZE)*-1;
				gemId = newGem.row.toString() + newGem.column.toString();
				gemsToCollapse[gemId] = newGem;
				addChild(newGem);
			}
			colapseGems(gemsToCollapse);
		}
		
		private function colapseGems(gemsToColapse:Object):void
		{
			var tween:Tween;
			var finalY:int;
			var score:int = 0;
			for each (var gem:Gem in gemsToColapse)
			{
				score++;
				finalY = GEM_OFFSET * gem.row + gem.row * CEL_SIZE;
				tween = new Tween(gem, "y", Strong.easeIn, gem.y, finalY, 0.4, true);
			}
			var event:ScoreEvent = new ScoreEvent(ScoreEvent.NEW_SCORE);
			event.score = score;
			dispatchEvent(event);
			tween.addEventListener(TweenEvent.MOTION_FINISH, gemsFinishedCollapsingHandler);
		}
		
		private function gemsFinishedCollapsingHandler(event:TweenEvent):void
		{
			Tween(event.currentTarget).removeEventListener(TweenEvent.MOTION_FINISH, gemsFinishedCollapsingHandler);
			
			//recheck for matches
			var queue:Array = [];
			var d:Dictionary = new Dictionary();
			var matches:Array;
			for each (var gem:Gem in gemsToCollapse)
			{
				matches = checkForMatches(gem);
				if (matches.length>0) 
				{
					queue = queue.concat(matches);
				}
			}
			gemsToCollapse = {};
			
			if (queue.length>=3)
			{
				destroyGems(queue);
			}
			else
			{
				selectedGems = new Vector.<Gem>();
			}
		}
	}
}